package apibasej.db.server.exec;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.logging.Level;

import apibasej.db.server.ConstantsServerDB;
import apibasej.db.to.PaginationTransformer;
import apibasej.db.to.StatementQuery;
import apibasej.db.transaction.CursorSelect;
import apibasej.db.transaction.GenericTransaction;
import apibasej.db.transaction.UtilDBException;

public class CursorSelectImplJdbc extends CursorSelect implements ConstantsServerDB{

	private PreparedStatement st;
	//private long tIniSelTemp;
	private ResultSet resultSet;
	private ResultSetMetaData metaData;
	private ArrayList<String> nameCols;
	private int numRowsRead = 0;
	private GenericTransaction tr;
	
	private StatementQuery sv;
	
	public CursorSelectImplJdbc(StatementQuery sv, GenericTransaction tr, Connection conn) throws UtilDBException {
		this.tr = tr;
		this.sv = sv;
		try{
			//tIniSelTemp = System.nanoTime();
			
			String sql = sv.getSql();
			
			if(sv.getPaginationTransformer()!=null){
				PaginationTransformer pt = (PaginationTransformer)sv.getPaginationTransformer().newInstance();
				sql = pt.transformSelectForPagination(sv.getFirstResult(), sv.getMaxResults(), sv.getSql());
			}
			
			// transforma para paginação
			if(sv.isReturnTotalCountWithoutPagination()){
				if(sv.getPaginationTransformer()==null) throw new UtilDBException("PaginationTransformer cannot be null");
				if(sv.getFirstResult()==null) throw new UtilDBException("FirstResult cannot be null");
				if(sv.getMaxResults()==null) throw new UtilDBException("MaxResults cannot be null");
			}
			
			st = UtilJDBC.createPreparedStatement(sql,sv.getParams(),conn); //tr.getConnection()
			//st.setQueryTimeout(db.getQueryTimeoutSeconds());// não é implementado em alguns drivers !!!
			//ClientUtil.finest("StatementQuery: "+sv.getSqlInfo());
			if(tr.getProp(DEBUG_SQL)){
				tr.logDef(Level.FINE, "StatementValues: "+sv.getSqlInfo(), null);
			}
			resultSet = st.executeQuery();
			metaData = resultSet.getMetaData();
			nameCols = new ArrayList<String>();
			for(int i=1; i<=metaData.getColumnCount(); i++){
				nameCols.add(metaData.getColumnName(i));
			}
			numRowsRead = 0;// não precisava, somente para manter padrão
		}catch (Throwable e) {
			tr.verifyConnectionAfterError();// deve ser chamado antes do close()
			tr.close();
			throw new UtilDBException("Error on execute SELECT", e, sv, null);
		}

	}

	
	// o index começa com 0 
	@Override
	public Object getValResultSet(Class<?> typeReturn, boolean ignoreResultNullForPrimitives, int index) throws SQLException{
		
		//TODO colocar estes tipos no UtilStrObj !!!!!!!
		
		Object val;
		// colocar os mais prováveis primeiro 
		if(typeReturn==null) val = resultSet.getObject(index+1);
		else if(java.lang.String.class.equals(typeReturn)) val = resultSet.getString(index+1);
		else if(java.lang.Integer.class.equals(typeReturn)) val = resultSet.getInt(index+1);
		else if(java.lang.Double.class.equals(typeReturn)) val = resultSet.getDouble(index+1);		
		else if(java.lang.Long.class.equals(typeReturn)) val = resultSet.getLong(index+1);				
		else if(java.lang.Float.class.equals(typeReturn)) val = resultSet.getFloat(index+1);			
		else if(java.sql.Date.class.equals(typeReturn)) val = resultSet.getDate(index+1);
		else if(java.sql.Timestamp.class.equals(typeReturn)) val = resultSet.getTimestamp(index+1);		
		else if(java.sql.Time.class.equals(typeReturn)) val = resultSet.getTime(index+1);
		else if(java.sql.Blob.class.equals(typeReturn)) val = resultSet.getBlob(index+1);
		else if(java.lang.Short.class.equals(typeReturn)) val = resultSet.getShort(index+1);				
		else if(java.lang.Boolean.class.equals(typeReturn)) val = resultSet.getBoolean(index+1);
		else if(java.sql.Array.class.equals(typeReturn)) val = resultSet.getArray(index+1);
		else if(java.sql.Clob.class.equals(typeReturn)) val = resultSet.getClob(index+1);						
		else if(java.lang.Byte.class.equals(typeReturn)) val = resultSet.getByte(index+1);
		else if(java.io.InputStream.class.equals(typeReturn)) val = resultSet.getBinaryStream(index+1);						
		else if(byte[].class.equals(typeReturn)) val = resultSet.getBytes(index+1);		
		else val = resultSet.getObject(index+1);
		
		// O metodo wasNull() informa se o último valor retornado era null. 
		//É importante para nos casos que retornam tipos primitivos default invés de null como getInt() ...
		if(!ignoreResultNullForPrimitives && resultSet.wasNull()) {
			return null;
		}else{
			return val;
		}
	}
	@Override
	public boolean next() throws SQLException{
		boolean b = resultSet.next();
		if(b) numRowsRead++;
		return b;
	}
	
	@Override
	public void verifyCloseConnectionAfterExecute(){
		if(resultSet!=null) try{resultSet.close();}catch (Throwable t) {} //getConfig().getLogger().log(Level.SEVERE,"Error on close ResultSet", t);
		if(st!=null) try{st.close();}catch (Throwable t) {} //getConfig().getLogger().log(Level.SEVERE,"Error on close Statement", t);
		tr.verifyCloseAfterExecute();
	}
	@Override
	public void close() {
		if(resultSet!=null) try{resultSet.close();}catch (Throwable t) {} //getConfig().getLogger().log(Level.SEVERE,"Error on close ResultSet", t);
		if(st!=null) try{st.close();}catch (Throwable t) {} //getConfig().getLogger().log(Level.SEVERE,"Error on close Statement", t);
		tr.close();
	}
	
	@Override
	public int getNumRowsRead() {
		return numRowsRead;
	}

	@Override
	public ArrayList<String> getNameColsStr() throws Exception {
		return nameCols;
	}
	@Override
	public int getCtCols() throws Exception {
		return nameCols.size();
	}
	
	@Override
	public Integer getTotalCountWithoutPagination() throws Exception{
		String sqlSelectForCount = transformQueryToCount(sv.getSql());
		StatementQuery svCount = new StatementQuery();
		svCount.setSql(sqlSelectForCount);
		svCount.setParams(sv.getParams());
		CursorSelect selCount = tr.executeQuery(svCount);
		selCount.next();
		return (Integer)selCount.getValResultSet(Integer.class, true, 0);
	}
	
	//return "SELECT COUNT(*) FROM( "+strQuery+" ) AS ctLinsAux";// ver opção de mudar este método para o ManagerDB se algum banco não aceitar
	private static String transformQueryToCount(String strQuery){
		StringBuilder sbQuery = new StringBuilder(strQuery);
		// DA ERRO EM ALGUNS CASOS. * VER FINAL DESTE ARQUIVO COMO EXEMPLO
		String qUpper = sbQuery.toString().toUpperCase();
		int posOrderBy = qUpper.lastIndexOf("ORDER BY"); //precisa tirar ORDER BY para tentar melhorar desempenho ???
		if(posOrderBy>=0) sbQuery.delete(posOrderBy,sbQuery.length());
		
		int posFrom = qUpper.indexOf("FROM");
		
		String strSel = sbQuery.substring(0,posFrom);// pega string antes do from
		strSel = strSel.split(",")[0].trim(); // se tiver vígulas(ou não) quebra e pega o primeiro pedaço
		String primCol = strSel.substring(strSel.lastIndexOf(" ")+1); //e com este pedaço pega a str após o último espaço
		
		sbQuery.replace(0,posFrom, "SELECT COUNT(DISTINCT "+primCol+") ");
		
		return sbQuery.toString();
	}
}
